package org.luxor.commons.jdbc.plugin.encrypt;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.luxor.commons.core.utils.StringUtils;
import org.luxor.commons.core.utils.encrypt.AesEncryptUtils;
import org.luxor.commons.jdbc.annotation.EncryptField;
import org.luxor.commons.jdbc.plugin.dynamic.properties.DynamicDataSourceProperties;
import org.luxor.commons.jdbc.utils.ReflectUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Wrapper;
import java.util.*;

/**
 * 对象属性脱敏处理拦截器
 *
 * @author Mr.Yan  @date 2019/9/30
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class EncryptFieldInterceptor implements Interceptor {

    /**
     * 最大递归次数
     */
    private final static int RECURSION_TIME = 10;

    private final DynamicDataSourceProperties properties;

    public EncryptFieldInterceptor(DynamicDataSourceProperties properties) {
        this.properties = properties;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        Object parameter = args[1];

        //加密参数
        doSecurityParse(parameter, 1);

        //执行切面的方法（保存数据）
        Object returnValue = invocation.proceed();

        //解密参数
        doSecurityParse(parameter, 0);

        //解密返回数据
        handleField(returnValue, 0, 0);
        return returnValue;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //.ignore
    }

    public void doSecurityParse(Object parameter, int type) {//type: 1加密,0解密

        //mybatis-plus插件有时会擅自将参数封装成HashMap再操作，此时需要取出对应的参数
        if (parameter instanceof HashMap) {
            HashMap<String, Object> hashMap = (HashMap<String, Object>) parameter;
            List<Object> handledParam = new ArrayList<>();
            for (Map.Entry<String, Object> entry : hashMap.entrySet()) {
                if (handledParam.contains(entry.getValue())) {
                    continue;
                }
                doSecurityParse(entry.getValue(), type);
                handledParam.add(entry.getValue());
            }
        } else if (parameter instanceof Set) {
            Set<Object> hashSet = (HashSet<Object>) parameter;
            for (Object entry : hashSet) {
                doSecurityParse(entry, type);
            }
        }
        if (parameter != null && !isBaseType(parameter)) {
            handleField(parameter, type, 0);
        }
    }

    /**
     * 递归对对象加解密的方法
     *
     * @param parameter
     * @param type      1加密,0解密
     */
    public void handleField(Object parameter, int type, int time) {
        time++;
        if (time > RECURSION_TIME) {
            //如果递归超过10次就不继续进行了，防止java.lang.StackOverflowError
            return;
        }
        try {
            //如果对象为ArrayList，遍历对象递归本方法
            if (parameter instanceof ArrayList<?>) {
                List<?> list = (ArrayList<?>) parameter;
                for (Object val : list) {
                    if (val != null && !isBaseType(val)) {
                        handleField(val, type, time);
                    }
                }
            }

            if (parameter == null || isBaseType(parameter)) {
                return;
            }

            //通过反射遍历对象的属性
            Field[] fields = ReflectUtils.getClassFieldsAndSuperClassFields(parameter.getClass());
            EncryptField encryptField;
            String obj;
            for (Field field : fields) {
                field.setAccessible(true);
                Object object = field.get(parameter);
                if (object != null && !isBaseType(object)) {
                    handleField(object, type, time);
                }
                //查询该属性是否带有EncryptField注解
                encryptField = field.getAnnotation(EncryptField.class);
                if (encryptField == null) {
                    continue;
                }
                obj = (String) field.get(parameter);
                if (obj != null) {
                    //对属性的值进行加解密处理
                    field.set(parameter, doEncrypt(obj, type));
                }
                field.setAccessible(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 对字符串加解密的方法
     *
     * @param str
     * @param type 1加密,0解密
     * @return
     */
    public String doEncrypt(String str, int type) {
        String result;

        if (StringUtils.isBlank(properties.getEncryptSecret())) {
            result = type == 1 ? AesEncryptUtils.encrypt(str) : AesEncryptUtils.decrypt(str);
        } else {
            result = type == 1 ? AesEncryptUtils.encrypt(str, properties.getEncryptSecret()) : AesEncryptUtils.decrypt(str, properties.getEncryptSecret());
        }
        if (type != 1 && result == null) {
            return str;
        }
        return result;

    }

    /**
     * 判断对象是否基本类型以及其他无需处理的对象类型
     *
     * @param object
     * @return
     */
    public static boolean isBaseType(Object object) {
        if (object instanceof Integer ||
                object instanceof Byte ||
                object instanceof Long ||
                object instanceof Double ||
                object instanceof Float ||
                object instanceof Character ||
                object instanceof Short ||
                object instanceof Boolean ||
                object instanceof String ||
                object instanceof BigDecimal ||

                object instanceof Map ||
                object instanceof List ||
                object instanceof Set ||
                object instanceof Iterator ||
                object instanceof Date ||
                object instanceof Array ||
                object instanceof Wrapper ||
                object instanceof com.baomidou.mybatisplus.core.conditions.AbstractWrapper ||
                object.getClass().isEnum()
        ) {
            return true;
        }
        return false;
    }
}
